﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Markup;
using SDF.Pagination;

namespace SDF
{
    [ContentProperty("Blocks")]
    public class TableCell : SmartBlock, IBlockContainer
    {
        #region Constructor

        public TableCell()
        {
            Border = new Border();
            Fragment = new Fragment() { Margin = new Thickness(0) };
            Border.Child = Fragment;
            Border.ClipToBounds = true;

            SmartLogicalTreeHelper.AddChild(this, Fragment);
        }

        #endregion

        #region DependencyProperties

        public static readonly DependencyProperty BorderBrushProperty = 
            System.Windows.Documents.TableCell.BorderBrushProperty.AddOwner(typeof(TableCell));

        public static readonly DependencyProperty BorderThicknessProperty =
            System.Windows.Documents.TableCell.BorderThicknessProperty.AddOwner(typeof(TableCell));

        public static readonly DependencyProperty ColumnSpanProperty = 
            System.Windows.Controls.Grid.ColumnSpanProperty.AddOwner(typeof(TableCell));

        public static readonly DependencyProperty RowSpanProperty = 
            System.Windows.Controls.Grid.RowSpanProperty.AddOwner(typeof(TableCell));

        public static readonly DependencyProperty ColumnProperty =
            System.Windows.Controls.Grid.ColumnProperty.AddOwner(typeof(TableCell));

        public static readonly DependencyProperty RowProperty =
            System.Windows.Controls.Grid.RowProperty.AddOwner(typeof(TableCell));

        #endregion

        #region Properties

        protected Border Border { get; private set; }

        protected Fragment Fragment { get; private set; }

        public SmartBlockCollection Blocks
        {
            get { return Fragment.Blocks; }
        }

        public Brush BorderBrush
        {
            get { return (Brush)GetValue(BorderBrushProperty); }
            set { SetValue(BorderBrushProperty, value); }
        }

        public Thickness BorderThickness
        {
            get { return (Thickness)GetValue(BorderThicknessProperty); }
            set { SetValue(BorderThicknessProperty, value); }
        }

        public int ColumnSpan
        {
            get { return (int)GetValue(ColumnSpanProperty); }
            set { SetValue(ColumnSpanProperty, value); }
        }
        
        public int RowSpan
        {
            get { return (int)GetValue(RowSpanProperty); }
            set { SetValue(RowSpanProperty, value); }
        }

        public int Column
        {
            get { return (int)GetValue(Grid.ColumnProperty); }
            internal set { SetValue(Grid.ColumnProperty, value); }
        }

        public int Row
        {
            get { return (int)GetValue(Grid.RowProperty); }
            internal set { SetValue(Grid.RowProperty, value); }
        }

        #endregion

        #region Visual Children Implementation

        protected override int VisualChildrenCount
        {
            get { return 1; }
        }

        protected override Visual GetVisualChild(int index)
        {
            if (index < 0 || index >= 1)
                throw new ArgumentOutOfRangeException("index");

            return Border;
        }

        #endregion

        #region PropertyChanged

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            if (e.Property == BorderBrushProperty)
            {
                Border.BorderBrush = this.BorderBrush;
            }
            else if (e.Property == BorderThicknessProperty)
            {
                Border.BorderThickness = this.BorderThickness;
            }
            else if (e.Property == ColumnSpanProperty)
            {
                Grid.SetColumnSpan(Border, this.ColumnSpan);
            }
            else if (e.Property == RowSpanProperty)
            {
                Grid.SetRowSpan(Border, this.ColumnSpan);
            }
        }

        #endregion

        #region Layout Methods

        protected override Size MeasureOverride(Size availableSize)
        {
            Border.Measure(availableSize);
            return Border.DesiredSize;
        }

        protected override Size ArrangeOverride(Size availableSize)
        {
            Border.Arrange(new Rect(availableSize));
            return availableSize;
        }

        #endregion

        #region Padding Methods

        protected override Thickness CalculateActualPadding()
        {
            double left = Padding.Left;
            double top = Padding.Top;
            double right = Padding.Right;
            double bottom = Padding.Bottom;

            if (double.IsNaN(left))
                left = LineHeight;
            if (double.IsNaN(top))
                top = LineHeight;
            if (double.IsNaN(right))
                right = LineHeight;
            if (double.IsNaN(bottom))
                bottom = LineHeight;

            return new Thickness(left, top, right, bottom);
        }

        #endregion

        #region IsValidParent

        public override bool IsValidParent(DependencyObject parent)
        {
            return parent is TableRow;
        }

        #endregion

        #region Harvesting

        public override SmartBlock GetEmptyCopy()
        {
            var result = new TableCell();
            CopyProperties(result);
            result.ColumnSpan = this.ColumnSpan;
            result.BorderBrush = this.BorderBrush;
            result.BorderThickness = this.BorderThickness;
            return result;
        }

        public override HarvestResult Harvest(HarvestingContext context, double remainingHeight, SmartBlock previousBlock)
        {
            var harvestedCell = this.GetEmptyCopy() as TableCell;

            if (this.Blocks.Count == 0)
            {
                // If it has no content, mark it as fully harvested
                context.MarkAsFullyHarvested(this);
            }
            else
            {
                // Harvest the content
                SmartBlock previousChild = null;
                double previousBottomMargin = 0;
                foreach (var child in this.Blocks)
                {
                    if (context.IsFullyHarvested(child))
                        continue;

                    // Two adjacent margins will overlap, so we need to adjust remaining height accordingly
                    remainingHeight += Math.Min(previousBottomMargin, child.ActualMargin.Top);
                    
                    var childResult = child.Harvest(context, remainingHeight, previousChild);
                    remainingHeight = childResult.RemainingHeight;
                    previousBottomMargin = child.ActualMargin.Bottom;

                    // Let's see if child fit
                    if (childResult.HarvestedBlock == null)
                    {
                        break;
                    }

                    // Add child to harvested element
                    harvestedCell.Blocks.Add(childResult.HarvestedBlock);
                    previousChild = child;

                    if (this.Blocks.IndexOf(child) == Blocks.Count - 1)
                    {
                        // Last child. We are finished, if it is finished
                        if (context.IsFullyHarvested(child))
                            context.MarkAsFullyHarvested(this);
                    }
                }
            }

            return new HarvestResult(harvestedCell, remainingHeight);
        }

        #endregion

        #region CalculateMinHeight

        public override double CalculateMinHeight()
        {
            if (Blocks.Count > 0)
            {
                var firstBlock = Blocks[0];
                if (firstBlock != null)
                    return firstBlock.CalculateMinHeight();
            }

            return this.LineHeight;
        }

        #endregion

    }
}
